移动端数据应用工厂

这是一种针对腾讯云公共云数据多场景、多维度看板的数据可视化 low code 配置的解决方案,是可配置化的前端 low code 项目。

背景

腾讯云可能有 100+ 公有云产品。他们有着不同的分类,比如:产品维度、部门维度、客户维度、渠道维度、行业维度、销售维度、... 而对于这些数据应用层需求各式各样,例如管理者们十分关注对于腾讯云上的收入、客户数据的实时分布,并且希望能够在小程序、移动端就可以方便查看;而销售们则只关注负责销售产品更下钻的数据产品...

另外,现有 pc 的可搭建系统存在比较多的问题:

  1. pc 仪表盘项目很老,虽然也提供了响应式的移动端页面,但是因为是老项目,组件不够丰富(满足不了新的功能需求),历史包袱重。汇报看板这种对时间要求高的需求,仪表盘的迭代速度跟不上。
  2. 权限颗粒度问题,老的仪表盘权限颗粒度只到了表级别,如果希望有更新的颗粒度,比如行权限,经过评估改造成本大, 也怕会影响其他现有的表。
  3. pc 仪表盘功能丰富,存在大量的其他模块的代码,汇报看板用它显得很重。更希望有轻量级的应用,来快速迭代,并往可搭建、low code 的方向去做,以此来节省开发的人力。

因此,继续一个权限管控更细,迭代更快,开发更灵活的方案来做这个事。

配置

1. ui 前端组件渲染

属性名:

  • is
  • props
  • on
  • $attrs
  • $listener
  • children

关键词:

  • JSX
  • 可视化搭建
  • 组件传参

2. dataOptions 取数逻辑

属性名:

  • WorksheetId 工作表 id
  • Config 小计
  • Order 排序
  • Fields 字段筛选。 默认会拉出全部的字段,做字段筛选的。
  • Filters 值筛选
  • Variables 筛选和小计 ?

关键词:

  • Id 映射到实际的表名
  • Node 开发完成之后给到一份协议文档

前端

要点:

  1. 沉淀组件库
  2. markdown-demo-loader
  3. 如何提高组件、项目开发环境的编译速度。

关键词:

  1. jsx + vue + config
  2. 动态 component + is 标签渲染组件,传入 dataSource 渲染图块
  3. Module 层包裹 Component 层,拥有 loadData 的行为,在指定时机触发取数操作。另外根据 dataOptions 中的 handler 函数名,在取数回来之后,对数据做加工处理,供 Component 层做直接渲染。
    1. Module 也可以包裹多个 Component,用于多个相同组件共享一份渲染数据。
    2. Table, Line, List 组件所需的数据结构是不同的,需要做额外的处理。这部分的逻辑最开始是写在 Module 组件的 Methods 中,不过这部分与业务逻辑几乎无关,已经在慢慢处理成 npm 包的形式了,一是便于单元测试,而是便于很方便的移植到 node BFF 层去。
    3. Module 层设计了一些钩子函数:
      1. onShow 判断组件是否可见,如果不可见的话,就不会继续后面的钩子。
      2. beforeLoad 会根据 dataOptions 中的传参,在发请求之前处理一下请求参数 options
      3. loaded 已经触发的取数操作,一般会在这里做数据的规范化, 处理 rowData 成为 dataSource
      4. error 会将 error 传到上层组件中做统一处理。
    4. 如果通用的配置和数据函数还是不能满足,或者统一在 Module 出配置不够灵活的话,每一个 Component 其实可以自定义补充一些处理函数,或者是自定义事件。
  4. Modules 层会包裹多个 Module。Tabbars, Tabs 会包裹 Modules 组成一个个的看板。 同时这几个模块都是继承 Module 的,都拥有这些钩子和取数能力。
  5. 筛选条件一类的组件,没有取数逻辑,只会触发事件, 或者在组件设计的时候,我们会考虑受控和非受控。非受控简单理解为,没有传值和事件进去,也能够正常工作,而受控则需要额外传入属性和事件才可以工作。一般会考虑设计成受控的,在 vue 的 jsx 中,需要重新通配 vModel 的能力,也就是给大多数筛选组件都定义 model 自定义表单。来控制 Module/Modules 级别的 state.

开发流程

  1. 总的一个配置文件。 一份配置文件,对应一个 App
  2. auth 接口登录, 拉取 tabs, tabbars 的权限,从 App Config 中筛选拼接出当前角色的 Config
  3. 动态注册路由,addRoute。 其他的前端项目一般会考虑,全部路由都注册, 但是通过 router 的 meta 携带参数来判断页面是否有权限。 这样子,前后端都需要维护一份角色、权限的配置信息。 另外一个用户可能包含多个角色,每次做一次筛选也不方便。这样做至少只需要维护一份权限配置即可。
  4. atomConfig to chart

数据来源

  1. 计费平台的流水表, 例如 cos 100 元,200 元等
  2. 数仓的同学,处理 tdw, 写 Python 脚本开发 日表、月表、季表等
  3. 数仓同学最终会通过洛子任务,将数据出库到结果库中,这里就是数仓的主题层?结果库中包含很多其他的数据库,例如:
    1. mysql
    2. clickHouse
    3. PG
    4. ...
  4. 工程组这里会拿一些宽表,做一些拼接,比如补充说明之类的,或者多表关联,这是在线的工作表做的事情。当然,针对一份数据宽表,因为数据的隐私性,需要加入行权限,那这里的权限筛选也是在工作表(这个服务中)处理的。 所谓的权限筛选,其实在真正的结果数据中,是不存在的。
    1. 权限的定义和维护,需要依赖另一个平台,定义了角色和权限点的概念。维护了一位同事与角色之间的关联关系。
    2. 表结构的基本样子:
      1. ftime
      2. gid
      3. cid
      4. uin
      5. productionName
      6. companyName
      7. ...
    3. 对于同一张宽表,以客户维度筛选、或者以公司维度筛选,如果处理成多个接口,肯定是不划算的。这里只需要通过不同的传参,来小计某一列即可。就可以做到搜 cid, gid 级别。TODO: 小计???
      1. sum ?
      2. with rollup ?

Node 逻辑

取数粘合层。

  1. 拼接图块的数据筛选条件, 配置指定筛选、过滤出的字段信息。
  2. 对当前表数据做权限筛选, AuthFilters
  3. 将条件拼接之后,执行动态 SQL 获取数据结果;或者将条件转化为底层元数据取数(多种数据库类型的抹平层)所需的条件,最终获取数据。

拼接图筛选条件

  1. 权限点
  2. select xxx from yyy where
    1. Filters
    2. Config
    3. Variables
    4. Fields
    5. WorksheetId

优点

  1. 开发更快
  2. 开发流程更快,各部分人力沟通成本小,各方面工作量都减少了。

不足

  1. 开发环境下,可能就是这么直接用 Node 从结果库中取数据了。 一般如果宽表不是很大的话,速度慢的没有那么明显,但是如果没有优先处理好输出,直接这样拿出来的数据就会很大。
  2. 部分接口是实时 SQL,目前的缓存策略,较为暴力。
  3. 取数任务服务会经过 redis 缓存,定时任务 mock 角色来预加载缓存,但是因为页面越来越多,定时任务 mock 所有角色跑所有页面的数据也不是很科学,未命中 cache 的话, 速度就会很慢。优化措施如下:
    1. 底表接着分库分表。
  4. 开发环境下, 无法做到颗粒度很细的 render ??? 常常因为修改了一个图块的配置信息,而导致整个页面都重新加载了。

渲染加速

  1. 虚拟列表
  2. 数据懒加载

技术点

  • vue
  • 可视化
  • echart
  • 通过 json 配置文件生成目录结构
  • 小程序 + webview

权限

权限颗粒度

  1. 行数据
  2. 项目(路由、模块)
  3. ?? 是啥来着?

权限管控

  1. 权限点
  2. 角色

权限链路

  1. mysql 配置库中,对应设置了一个字段属性,node 中将它读取出来之后,会根据里面的内容,做数据筛选。

角色与权限

前端对于角色的权限解决有两种方案:

  1. router 全量注册,通过 beforeEnter 钩子前端来判断当前角色是否有权限进入该页面
  2. 默认只注册不设权限的页面,通过 auth 接口拿到有权限的列表,通过 filter 全量的配置文件内容,addRoutes 动态注册有权限的路由。

后者比前者好的点:权限问题后台也需要拉取,前者需要两边都拉取,用映射关系各自做权限判断处理,一来两边都计算会不统一,修改权限需要改两处,二来权限 map 全量返回前端或者前端直接写死有泄漏风险。

后者缺点:需要额外做一次配置的筛选操作,返回给前端处理。

为什么要做一个移动端的数据应用?

  1. pc 仪表盘迭代速度跟不上,图表组件库不够丰富。
  2. pc 仪表盘的业务逻辑很重,系统优化历史包袱重,充满了不同环境、入口的兼容性代码,大量的 if else 判断环境。 针对汇报看板这个场景,希望有一个轻量、高性能的应用让 leader/GM 有更好的体验。
  3. 数据应用对数据的权限控制颗粒度更细,需要更加灵活的支持多种角色的权限管控,所以更适合站在巨人的肩膀上另起炉灶。

怎么做的?

TODO: 重点原理

  • 前端: 基于 vue JSX + 自定义的 JSON Schema 规范。分为 ui 和 dataOptions 属性,分别对应 ui 和取数逻辑。
    • 组件库的处理。 vant + echarts + table + 自定义的图表组件。
  • Node: 基于 SQL 语法定义的自定义 JSON Schema 搜索语句。
    • worksheetId 数据源
    • Filters 筛选
    • Configs 小计
    • Variables 条件配置

步骤

  1. 首先定义了一个渲染的基类组件 comp, 注册了很多 components, 并传入 is 表示组件名,dataSource 用于渲染组件内容的数据。
  2. 定义最小模块 module,它拥有 beforeLoad, loaded, Error 几个钩子函数。 需要传入 ui 和 dataOptions 两个属性
  3. 多个 module 可以组成一个 modules, 就可以直接拼装成一个页面。默认情况下,modules 不会自动发出请求,因为不会给它传 dataOptions,但是实际上 modules 是从 module, 它也拥有单独取数的能力。
  4. 组件的几种分类:
    1. 数据渲染组件,例如 table, line,条形图、柱状图等,在封装了一层 echarts 的情况下, 这些组件都是直接在 comp 组件中注册的,意味着只要给 comp 传 is 和 dataSource 就可以正常渲染。
    2. 筛选的功能组件,这种组件 change 之后会改变取数的条件,用于更新数据。 这里就需要考虑如何与事件消费中心打通,
renderChild

一个主 Vue 实例渲染所有组件的形式。

问题:

  1. 最大的问题之一, bff 层的对接,不是直连后台服务。解决方式: 手动填入数据,或者接口
  2. 组件生态不够好,设计师设计稿很好看,前期组件沉淀还不够,不能够迅速搭建起移动端页面。
  3. 传统的可视化搭建系统存在的最大问题是,基本上都是单个页面的,比如活动页,表单收集页等,因为路由切换、多页数据共享等逻辑会与传统可搭建系统的纯粹性产生冲突,当然也不排除一些业务本身的场景比较简单。从编译的角度上来做的 搭建系统就可以解决这个问题,所谓的搭建,其实就是在写 dom json 。

与其他业界的 low code 项目的区别在哪里?

成果

经过该数据应用工厂底座能力创建的数据应用已经有 5 款,上线速度快,效果体验好。

搭建系统的优点和不足

亮点:

  1. mock 平台,做了数据权限管控和模拟用户的功能,能够让定时任务很好每天定时跑,一来预加载缓存,二来作为一个接口的自动化测试
  2. 节省了很多的人力

不足:

  1. 实时 SQL,加入的缓存是对参数 md5 之后的缓存,直接而强硬。

优点:

  1. 开发时间更快,数仓、后台、前端三个方向的开发工作量都更小了。

维度

  • 产品
  • 客户
  • ...

表命名

  • 收入明细表
  • 毛利表
  • top 损益
  • top 收入客户等

难点

项目最大的难点

以前的难点基本都已经过去了。现在难? 产品迭代快,换了几波人,没有按照最开始设计的样子进行提

  1. 多个卡片公用一个数据;一个页面里有公用的时间筛选器,也有卡片自己维护的。
  2. 取数接口太纯粹,没有任何的输数据处理逻辑。这就导致没法简单地在前端直接拖拽,需要先填数据源,筛选条件,数据处理函数(产品、运营是不知道有什么函数可以调用的。。。)
    1. 同事计划新开一个服务,去做数据转化
    2. 这样可以也可以,但是我觉得这里还是用中间件的形式去处理比较好。

同比 环比

一级行业提取一个分类筛选。时间打横,二级行业作为维度,收入值是指标。

每一行增加一行该一级行业的小计。

分成了两个接口,请求。因为接口取数逻辑太纯粹,没法额外请求一次。

  • low code 设计,解决的问题,收获,还存在的问题 ???

项目难点

  1. 问项目难点引入到重构, 在项目中做了什么事情有成就感,也引入到重构老项目。 成果: 开发速度的提升,工程、流程化规范了之后,减少发布、回滚的心智负担。
  2. 接手老的前端项目,接入监控系统,懒加载等性能优化。

最大的难点

我的工作中最大难点是,有时候会遇到技术边界以外的问题,我有两个解决办法,一是快速学习,二是快速搜索别人的现成方案

举个例子,我们蒜厂主站重度使用了 CodeMirror 这个轮子(我在它上面封装了七八个模块),然而这个轮子的文档写得并不好,很多时候我们会遇到一些需求,而这些需求文档里又写得非常模糊,这就比较头疼了。所以很多时候,我就要去不断地从文档的字里行间猜测,并结合源码一步一步地去跟踪,去尝试解决这个问题。但有时候确实超出了我的能力范围,那么我就会把我的问题提炼成一个小 demo,到 StackOverflow 去问,或者问一些同样用这个轮子的作者,甚至干脆去 issue 里问作者

(前端)难点

主要一块还是在数据通信。 因为换了几波产品经理、几波设计,会有多种场景的数据交互:

  1. 一个页面多个图表,公用同一个数据源,同一个筛选条件,也可能不同的筛选条件
  2. 多个页面共享一个筛选条件
  3. 同个页面,有多个触发重新取数的操作。
  4. 针对上面的几种公用数据的场景,存在:
    1. vuex 传统的使用方式会不够领会
    2. vue bus 在一定场景下面,会更灵活好用
  5. 数据的权限控制,沉淀了另一个平台:数据资产平台。
  6. 每个月固定跑数,一开始需要人工打开配置开关。
    1. 配置中心配置。 开发得到产品的指令, 手动配置。 开关开启、关闭,开发会对线上环境负责。
    2. 工作表配置。 project 颗粒度的权限,交给产品自己配置。产品权限太大, 不会关注、测试所有的模块,这是一个隐患。
    3. 上面都是人工手动进行修改。 这边是希望能够跟企业微信机器人打通,通过 api 的形式去处理。

数据通信

主要的难点是在于数据通信:

  1. 一个页面多个图表,用同一个数据源(需要减少请求)
  2. 多个页面共享一个筛选条件
  3. 同一个页面有多个触发条件取数的操作
  4. 每个月固定日期出数:
    1. 到时间,人工打开配置开关
    2. 定时任务修改配置文件,通过企业微信机器人对话即可自动修改配置(最主要的目的是控制出数的人不需要上配置平台,减少操作心智)

数据通信的解决方案:

  1. 直接写 vuex 肯定不够灵活。考虑动态加载模块 ?
  2. vue bus 自定义事件的形式更好用。

取数事件触发

  1. 日、周、月切换;时间选择
  2. 维度切换

等条件切换之后都有可能会重新触发取数。 但是如果每一个事件触发都直接发一个请求的话,会有几个问题:

  1. 首先肯定是浪费资源
  2. 其次因为筛选的条件不一致,如果前一个请求没有及时退出,可能会因为底层数据的多少差别,而导致图表数据闪烁(多个请求返回)

解决:

  1. 封装 axios 的时候,需要根据取数 ID 来存储 axios 的 token, 如果多个请求发出,需要再下一个请求发出之前先调用 token.cancel 函数退出请求
  2. 设计一个简洁的事件消费中心对象,每一个取数模块需要预先注册取数的触发事件名,条件组件触发之后,消费中心接收到事件比对触发事件名,若匹配则发出请求。

权限处理

  1. 每一张仪表盘、报表、工作表、推送等都会有一个管理员 or 负责人的概念,对表这个级别做控制。
  2. 行业经分、产品分析等会内嵌一些仪表盘,这些仪表盘是不做权限管控的,所有人都需要拉出数据。这部分的操作是通过,在配置库中指定某张工作表从指定 channel 来的时候,会走 no-check.
  3. 行数据的权限管控,在磐石上将所有部门名、产品名都做了录入,创建角色与这些权限点进行绑定。在工作表、仪表盘(底层还是工作表取数)的时候,还是动态拼接上了行权限(枚举出所有的产品名做一个拼接),最终返回数据。
  4. 产品权限与前端路由权限。 磐石统一控制,前端拿到权限点之后,动态 addRoutes 添加路由。

3. 重构

背景

hc 少,有很多稍微老一点的项目,以及很多前端项目都是外包处理的。

解决方案:

  1. js 改 ts
  2. 加入测试用例
  3. 加入 ci cd
  4. 升级 vue react 的依赖
  5. 抽离大量的公共 npm 包
  6. 引入微前端的概念
  7. 考虑 iframe 的重构
    1. 目的是为了实现页面的复用
    2. 从组件、脚手架级别实现复用
      1. 组件库
      2. 脚手架
      3. 种子项目
    3. json 生成页面和模块的方案, 直接生成代码的方案

其他重构或者重新搭建一个项目会考虑的点:

  1. 技术栈的确定。
  2. 目录结构的约定 router/services/store/components 等, 全都通过 webpack 自动加载。
  3. 工具库 npm 的引入,包含 axios 拦截封装,功能 utils 函数等
  4. 监控系统的接入, 满足当前的业务埋点需求 TAM + Sentry
  5. CI/CD Git 分支与环境的对应, 全部通过提交 MR 合并之后才会发布代码,自动跑单测、自动构建发布对应的环境,正式 Master 发布之后自动打 tag 提交 changelog 至 Master 分支。
  6. Husky + Eslint 校验代码规范、commit message 规范。 单元测试通过之后才给提交。
  7. 与后台开发同学确定好接口之后,书写 mock json 到一个 git 项目中,node mock server 会根据 json 的内容提供 mock 接口内容,便于前端先进行开发

重构 vue 的项目

  1. 目录结构规范
    1. 统一 @ 路径
    2. 统一 eslint 与 prettier
  2. 接入 ci/cd
  3. 某些依赖升级,比如 vue webpack 等
  4. 单元测试

自动生成 mock 的服务

https://jsonplaceholder.typicode.com/

物料:

  • json demo
  • js 对象
  • class 类
  • d.ts 文件
  • pb 文件

通过词法分析,根据值或者类型来判断该 mock 的值的类型。通过数据资产平台,拉取数据,自动生成 mock 元素的结构。

namespace 与类、维度、指标的维护和共享。

  • MOCK
    • proxy: https://jsonplaceholder.typicode.com/ https://segmentfault.com/a/1190000008635891
    • json-server
  • 数据 mock 平台 https://github.com/thx/rap2-delos

基础服务

  • 企业微信机器人
  • 邮件
    • html 转 inline

项目中遇到过哪些问题

项目内部问题

  1. 项目关键资源不足
  2. 关键项目遇到瓶颈
  3. 项目内部管理混乱
  4. 项目核心骨干流失

跨项目协作问题

  1. 跨项目成员动力不足
  2. 项目相关方沟通不足

产品工作问题

  1. 需求方杂乱、需求密集
  2. 紧急需求打乱日常节奏
  3. 日常重复性工作耗时较多

遇到最困难的事情,项目中最难的问题

是这样的,具体什么困难是最让我头疼的我已经记不清了,因为遇到困难这件事本身就是客观存在的,只要一个人还在前进,就会不断遇到困难。我在学习上、生活上不断解决各种问题的过程中得出了一些经验,这些经验让我成长了很多。比如,遇到困难时一定要保持心态良好,不要气馁和沮丧。如果要做一件事情频繁遇到困难,或者遇到的困难看起来很棘手,经验告诉我,那通常是路走错了,或者还没有找到正确的解决方式。这个时候保持心态良好,不气馁就很重要了。比如:之前导师让我写一个架构的 demo,但是那个架构的技术栈很多我都没有了解过,我开始是按照任务清单挨个往下执行,但是这个过程频繁遇到很棘手的问题,于是,我觉得是我的执行方法有问题,在一个下午我花了几个小时的时间,重新整理了一下思路,我把任务分解了一下,把每个我不熟悉的模块单独拉出来,写一些和其他模块几乎完全不相干的 demo,等到所有模块都熟悉的差不多了,然后再整合起来写一个完整的流程,这样一来,这件事情就被我解决了。生活中也是一样,遇到棘手的问题,保持心态良好,冷静的去思考更好的解决方式即可。

这个问题主要考察:1、能不能清晰认知自己的错误-认知能力;2、面对挫折你做了什么-解决问题的能力;3、学习到什么,怎样避免再次出现-自我完善能力;4、顺便通过讲述观察语言有没有逻辑性。

真实

传统前端开发到数据前端开发的转型,成长的过程我认为是一个让人痛苦又让人兴奋的过程。来数平的前半年,差不多都在做数据应用的事情, 从行业经分系统到 low code 可视化配置的事情,基本上我作为一个前端开发需要关注的事情,但是不了解整个业务逻辑。虽然做的事,是很完整的前端,从 node 到 h5 到小程序出口。

数据资产平台的开发,让我了解到整个数据链路,数据上报到 tdbank,等等中间的流程。

作用和价值

  1. 减少人力开发。复用取数和逻辑的原子能力?

Json 这种形式跟直接写代码相比,好在哪里

为什么不让产品、运营配,解放开发的人力

技术的规划升级

不足之处:

  1. 底层数据源来自大宽表,实际还是存在首次加载不够快的情况。
  2. 缓存机制是,请求的筛选参数值的 md5 值。意味着缓存跟某一个人是没有关系的,跟一个角色有关系。这样的话,只能暴力的清除某一个角色的缓存。
  3. 权限颗粒度分层明显,但不够灵活,直接取数的时候是可以绕过权限的逻辑,拿出原始数据。
  4. 有新的看板需求,前端同学压力大,新表接口需要写 node 配置,接着写前端的配置, 接着进行测试、预发、正式。
  5. 前端逻辑层, 很重。node 层取数逻辑很轻。 这样的设计,其实是沿用了,罗盘的取数逻辑。但是其实会存在不太合理之处,比如就工作量来说,如果前端和 node 同学一起加入,node 同学工作量会少很多。

解决方案:

  1. 数仓同学可以给到一定的支持,将权限部分的筛选结果直接跑到不同的细表里面去,node 做一个映射,保证数据的隐私性。
  2. 为了缓存,带权限接口与非权限接口有可能需要剥离??? 权限与缓存还需要再思考一下。
  3. 将公共的逻辑处理部分,比如将原始数据处理成 table 柱状图之类的纯函数逻辑,提取成 npm 包,慢慢一步步切到 serverless 中去。
  4. 业务方前端同学、或者内部开源的同学, 需要将文档、底层能力的接口、说明文档准备好,严格把控接口、组件的破坏性升级等。做好客服工作 ?

项目的问题

  1. json 中无法写函数,写的函数无法很好的注入 content 或者 this 之类的上下文,除非改造函数的写法,比如 vm => vm.data 之类的将 this 传入 解决: 参考 VS Code 中的 ui 可配置化,采用 when 的形式,对操作符进行处理,将 when 字符串拼接上 with(this) 执行的接口会被自动 return, 这样就可以直接写属性名的判断条件,而不用带上 this 之类的前缀

  2. 随着项目的更新,旧的配置解析器慢慢显示出一些问题(项目初期设计不算太完善),但是线上已经有多个项目在跑了,新功能要加入旧解析器不能再很好的工作了。 解决: 优雅降级,对于老的组件,额外开发一些 transfor 转换器,将新配置(可读性更好)的配置转化成旧配置进行渲染; 对于新组件,直接将新的配置文件进行解析渲染。

  3. 可视化组件库的规范???

但是目前是手写 json 配置。

项目介绍

  1. 平台整个工作链路
  2. 介绍高管驾驶舱

low code 项目的价值在哪里? 提升了多少效率

Last Updated:
Contributors: yiliang114